package panel

import (
	"image/color"
	"io/ioutil"
)

import "image"
import "image/draw"

import "code.google.com/p/x-go-binding/xgb"
import "code.google.com/p/freetype-go/freetype"
import "code.google.com/p/freetype-go/freetype/truetype"

type View interface {
	Key(rune int) View
	Resize(wh Size, Offset Point)
	FocusForClick(xy Point) View
	At(xy Point) View
	LoseFocus()
	GainFocus(xy Point)
	Cursor(xy Point)
	Parent(p View)
	Refresh()
	Put(xy Point, back Paper, p Pen, rune int)
	PutString(xy Point, back Paper, p Pen, text string)
	Show(message string)
	Box(xy1 Point, xy2 Point)
	Floaty(xy Point, text string)
}

type WindowPanel struct {
	pinfo *DrawInfo
	//
	restore       *xgb.GetImageReply
	restoreFloaty *xgb.GetImageReply
	floatAt       Point
	floatWH       Size
	floatText     string
	//
	con *xgb.Conn
	win xgb.Id
	gc  xgb.Id
	//
	cursor Point
	wh     Size
	body   View
}

const (
	pointSize  = 9
	resolution = 96
)

func topixels(tfont *truetype.Font, n int) int {
	return n * (pointSize * resolution) / (tfont.UnitsPerEm() * 72)
}

var RedPaper = PlainPaper{image.NewUniform(color.RGBA{255, 200, 200, 255})}

func NewDrawInfo() *DrawInfo {
	context := freetype.NewContext()
	regular := "/usr/share/fonts/liberation/LiberationMono-Regular.ttf"
	// fontfile := "/usr/share/fonts/dejavu/DejaVuSansMono.ttf"
	fontBytes, err := ioutil.ReadFile(regular)
	if err != nil {
		Log(err)
		return nil
	}
	tfont, err := freetype.ParseFont(fontBytes)
	if err != nil {
		Log(err)
		return nil
	}
	bold := "/usr/share/fonts/liberation/LiberationMono-Bold.ttf"
	fontBytes, err = ioutil.ReadFile(bold)
	if err != nil {
		Log(err)
		return nil
	}
	bfont, err := freetype.ParseFont(fontBytes)
	if err != nil {
		Log(err)
		return nil
	}
	//
	context.SetFont(tfont)
	context.SetFontSize(pointSize)
	context.SetDPI(resolution)
	context.SetSrc(image.Black)
	//
	f := func(n int16) int { return topixels(tfont, int(n)) }
	b := tfont.Bounds()
	// Log("font bounds: ", f(b.XMin), f(b.YMin), f(b.XMax), f(b.YMax))
	// hm := tfont.HMetric(tfont.Index('a'))
	// Log("font hmetric(A): ", "aw: ", f(int16(hm.AdvanceWidth)), "px; lsb: ", f(int16(hm.LeftSideBearing)))
	//
	blackPen := NewPlainPen(tfont, 0, 0, 0)
	altPen := NewPlainPen(tfont, 100, 0, 0)
	keyword := NewPlainPen(bfont, 120, 0, 100)
	builtin := NewPlainPen(bfont, 0, 0, 0)
	comment := NewPlainPen(tfont, 0, 200, 0)
	stringpen := NewPlainPen(tfont, 0, 200, 200)
	fatpen := NewPlainPen(tfont, 0, 255, 255)
	//	
	pi := &DrawInfo{
		context:       context,
		tfont:         tfont,
		char_height:   f(b.YMax - b.YMin),
		pixelsPerCol:  f(b.XMax - b.XMin),
		pixelsPerLine: f(b.YMax - b.YMin),
		pixelTweak:    f(b.YMin),
		// Thingy: (*PlainPen)(image.NewColorImage(image.RGBAColor{100, 0, 0, 255})), // (image.Black),
		Back:   PlainPaper{image.White},
		Cream:  PlainPaper{image.NewUniform(color.RGBA{255, 255, 200, 255})},
		Thingy: blackPen,
		Pens: map[string]Pen{"normal": blackPen,
			"keyword":   keyword,
			"builtin":   builtin,
			"comment":   comment,
			"string":    stringpen,
			"fatString": fatpen,
			"alt":       altPen,
		},
	}
	return pi
}

func NewWindowPanel(con *xgb.Conn, di *DrawInfo, body View, width, height int) *WindowPanel {
	// Log("NewWindowPanel: di", di)
	win := con.NewId()
	s := con.DefaultScreen()
	gc := con.NewId()
	con.CreateWindow(0, win, s.Root, 50, 50, uint16(width), uint16(height), 0, 0, 0, 0, nil)
	con.ChangeWindowAttributes(win, xgb.CWBackPixel|xgb.CWEventMask,
		[]uint32{
			s.WhitePixel,
			xgb.EventMaskExposure | xgb.EventMaskKeyRelease | xgb.EventMaskKeyPress |
				xgb.EventMaskButtonPress | xgb.EventMaskButtonRelease |
				xgb.EventMaskPointerMotion | xgb.EventMaskStructureNotify,
		})
	con.ChangeProperty(
		xgb.PropModeReplace,
		win,
		xgb.Id(39), // pre-defined atom WM_NAME
		xgb.Id(31), // pre-defined atom STRING
		8,          // units are bytes
		[]byte("Tide"),
	)
	//    
	con.CreateGC(gc, win, xgb.GCBackground|xgb.GCForeground, []uint32{s.BlackPixel, s.WhitePixel})
	con.MapWindow(win)
	//
	wp := &WindowPanel{
		pinfo: di,
		con:   con,
		win:   win,
		gc:    gc,
		body:  body}
	body.Parent(wp)
	return wp
}

func (w *WindowPanel) Refresh() {
	// func (c *Conn) ClearArea(Exposures bool, Window Id, X int16, Y int16, Width uint16, Height uint16)
	// Log("WindowPanel.Refresh")
	// width, height := uint16(w.cols * pixels_per_col), uint16(w.lines * pixels_per_line)
	// w.con.ClearArea(true, w.win, 0, 0, width, height )
	w.body.Refresh()
}

func (w *WindowPanel) Show(message string) {
	Log(message)
}

func (wp *WindowPanel) At(xy Point) View {
	// Log("WindowPanel.At")
	return wp.body.At(xy)
}

func (wp *WindowPanel) GainFocus(xy Point) {
}

func (wp *WindowPanel) LoseFocus() {
}

func (wp *WindowPanel) Resize(wh Size, offset Point) {
	if wh.X != wp.wh.X || wh.Y != wp.wh.Y {
		wp.wh = wh
		wp.body.Resize(wh, offset)
	}
}

func (w *WindowPanel) Parent(p View) {
	panic("cannot give a WindowPanel a parent")
}

func (w *WindowPanel) Key(rune int) View {
	return nil
}

func (w *WindowPanel) FocusForClick(xy Point) View {
	return w.body.FocusForClick(xy)
}

func (w *WindowPanel) Put(xy Point, back Paper, p Pen, rune int) {
	w.PutString(xy, back, p, string([]byte{byte(rune)}))
}

func (w *WindowPanel) Box(xy1, xy2 Point) {
	// draw a box
	x1, y1, x2, y2 := xy1.X, xy1.Y, xy2.X, xy2.Y
	w.line(Point{x1, y1}, Point{x2, y1})
	w.line(Point{x2, y1}, Point{x2, y2})
	w.line(Point{x2, y2}, Point{x1, y2})
	w.line(Point{x1, y2}, Point{x1, y1})
}

func (wp *WindowPanel) Cursor(xy Point) {
	wp.cursor = xy
}

func (wp *WindowPanel) Floaty(xy Point, text string) {
	// Log("Floaty: ", xy, text)
	wp.floatAt = xy
	wp.floatText = text
}

const (
	EXTRA = 1
)

func (wp *WindowPanel) ShowCursor() {
	{
		w, h := uint16(1), uint16(20)
		xy := wp.cursor
		x, y := wp.pinfo.Fix32ToPixels(xy)
		image, err := wp.con.GetImage(xgb.ImageFormatZPixmap, wp.win,
			int16(x+wp.pinfo.pixelsPerCol), int16(y), w, h,
			0xffffff)
		if err != nil {
			Log("ShowCursor: err = ", err)
		}
		wp.restore = image
		wp.draw_cursor(wp.cursor)
	}
	//
	{
		xy, text := wp.floatAt, wp.floatText
		if text != "" {
			rgba, width, height := wp.DrawText(wp.pinfo.Cream, wp.pinfo.Thingy, text)
			wp.floatWH = Size(wp.pinfo.PixelsToFix32(width, height))
			x, y := wp.pinfo.Fix32ToPixels(xy)

			image, err := wp.con.GetImage(xgb.ImageFormatZPixmap, wp.win,
				int16(x), int16(y), uint16(width), uint16(height),
				0xffffff)

			logIfError("WindowPanel.ShowCursor", err)
			wp.restoreFloaty = image
			wp.PutTextImage(xy, rgba, width, height)
		}
	}
}

func (wp *WindowPanel) HideCursor() {
	if wp.restore != nil {
		w, h := uint16(1), uint16(20)
		xy := wp.cursor
		x, y := wp.pinfo.Fix32ToPixels(xy)
		// wp.con.ClearArea(false, wp.win, 0, 0, w, h )
		wp.con.PutImage(xgb.ImageFormatZPixmap, wp.win, wp.gc,
			w, h, int16(x+7), int16(y),
			0, wp.restore.Depth, wp.restore.Data)
	}
	//
	if wp.restoreFloaty != nil {
		xy := wp.floatAt
		x, y := wp.pinfo.Fix32ToPixels(xy)
		width, height := wp.pinfo.Fix32ToPixels(Point(wp.floatWH))
		wp.con.PutImage(xgb.ImageFormatZPixmap, wp.win, wp.gc,
			uint16(width), uint16(height), int16(x), int16(y),
			0, wp.restoreFloaty.Depth, wp.restoreFloaty.Data)
		wp.restoreFloaty = nil
	}
}

func (wp *WindowPanel) draw_cursor(xy Point) {
	x, y := wp.pinfo.Fix32ToPixels(xy)
	// Log("draw_cursor:", xy, "==>", x, y)
	x16, y16 := int16(x+wp.pinfo.pixelsPerCol), int16(y)
	points := []xgb.Point{{x16, y16}, {x16, y16 + int16(wp.pinfo.char_height)}}
	wp.con.PolyLine(0, wp.win, wp.gc, points)
}

func (w *WindowPanel) line(xy1, xy2 Point) {
	x1, y1 := int(xy1.X>>8), int(xy1.Y>>8)
	x2, y2 := int(xy2.X>>8), int(xy2.Y>>8)
	points := []xgb.Point{{int16(x1 + 10), int16(y1 + 10)}, {int16(x2 + 10), int16(y2 + 10)}}
	w.con.PolyLine(0, w.win, w.gc, points)
}

func logIfError(context string, err error) {
	if err != nil {
		Log("oops:", context, err)
	}
}

func (wp *WindowPanel) DrawText(back Paper, p Pen, text string) (im *image.RGBA, w, h int) {
	// Log("text: '" + text + "' at ", x, y)
	var pixels_per_col = wp.pinfo.pixelsPerCol
	width, height := len(text)*(pixels_per_col+1), wp.pinfo.char_height
	rgba := image.NewRGBA(image.Rect(0, 0, width, height))
	// Log("w, h = ", width, ", ", height)
	// bg := image.NewColorImage(image.RGBAColor{255, 127, 127, 255})
	bg := back.PaperColor()
	draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Over)
	//
	// wp.pinfo.context.SetSrc(image.NewColorImage(image.RGBAColor{100, 0, 0, 255}))
	wp.pinfo.context.SetSrc(p.Color())
	wp.pinfo.context.SetFont(p.Font())
	//
	wp.pinfo.context.SetDst(rgba)
	wp.pinfo.context.SetClip(rgba.Bounds())
	_, err := wp.pinfo.context.DrawString(text, freetype.Pt(0, wp.pinfo.char_height+wp.pinfo.pixelTweak))
	logIfError("WindowPanel.DrawText", err)
	return rgba, width, height
}

func (wp *WindowPanel) PutString(xy Point, back Paper, p Pen, text string) {
	// Log("WindowPanel.PutString: ", xy, "<", text, ">")
	rgba, width, height := wp.DrawText(back, p, text)
	wp.PutTextImage(xy, rgba, width, height)
}

func (wp *WindowPanel) PutTextImage(xy Point, rgba *image.RGBA, width, height int) {
	x, y := int(xy.X>>8), int(xy.Y>>8)
	// Log("x, y: ", x, y)
	const (
		depth = uint8(24)
	)
	const (
		pad = uint8(0)
	)
	const (
		bytesPerPixel = 4
	)
	var (
		size = int(width) * int(height) * bytesPerPixel
	)
	bytes := make([]byte, size)
	bindex := 0
	for _, pixel := range rgba.Pix {
		bytes[bindex] = pixel
		bindex += 1
		// r, g, b, a := pixel.R, pixel.G, pixel.B, pixel.A
		// _ = r + g + b + a
		// bytes[bindex] = b; bindex += 1 // BLUE
		// bytes[bindex] = g; bindex += 1 // GREEN
		// bytes[bindex] = r; bindex += 1 // RED
		// bytes[bindex] = a; bindex += 1 // ALPHA
	}
	// Log("x, y = ", x, y, "; w, h = ", width, height)
	wp.con.PutImage(xgb.ImageFormatZPixmap,
		wp.win, wp.gc,
		uint16(width), uint16(height),
		int16(x), int16(y),
		pad, depth, bytes)
}
